#include "StdAfx.hpp"
#include "Gradients.hpp"
#include "D3dUtils.hpp"

namespace gradients {

void BaseGradient::RenderAll( COLORF *outColors, uint count )
{
	assert(count);

	float t = 0.f, tStep = 1.f / (count-1);
	for (uint i = 0; i < count; i++, t += tStep)
		GetColorAt(outColors[i], t);
}

void BaseGradient::Interpolate( COLORF &out, const COLORF *colors, uint colorCount, float t )
{
	if (colorCount == 0)
		out = COLORF_TRANSPARENT_;
	else
	{
		float x = t * colorCount;
		int index = (int)x;
		if (index < 0)
			out = colors[0];
		else if (index >= (int)colorCount-1)
			out = colors[colorCount-1];
		else
		{
			float xFrac = frac(x);
			Lerp(&out, colors[index], colors[index+1], xFrac);
		}
	}
}

void TextureGradient::Load( const string &filePath )
{
	MemoryTexture memTex(filePath);
	uint width = memTex.GetWidth();
	m_Colors.resize(width);
	const COLOR *texPixel = memTex.GetRowData(memTex.GetHeight()/2);
	for (uint x = 0; x < width; x++, texPixel++)
		ColorToColorf(&m_Colors[x], *texPixel);
}

void TextureGradient::GetColorAt( COLORF &out, float t )
{
	if (m_Colors.empty())
		out = COLORF_TRANSPARENT_;
	else
		Interpolate(out, &m_Colors[0], m_Colors.size(), t);
}


ApophysisGradient::ApophysisGradient()
{
	ZeroMemory(m_Colors, sizeof(m_Colors));
}

void ApophysisGradient::Load( const string &filePath, const string &gradientName )
{
	FileStream fs(filePath, FM_READ);
	CharReader cr(&fs);
	SkipWhitespace(cr);

	// For each gradient
	for (;;)
	{
		if (cr.End())
			throw Error(Format("Gradient \"#\" not found.") % gradientName);

		string name;
		ParseIdentifier(name, cr);

		ExpectSymbol(cr, '{');

		ExpectIdentifier(cr, "gradient");
		ExpectSymbol(cr, ':');

		if (name == gradientName)
		{
			ParseGradientContent(cr);
			return;
		}
		else
			SkipGradientContent(cr);

		ExpectSymbol(cr, '}');
	}
}

void ApophysisGradient::ParseIdentifier( string &outName, CharReader &cr )
{
	outName.clear();

	char ch;
	while (cr.PeekChar(&ch) && CharInIdentifier(ch))
	{
		outName += ch;
		cr.SkipChar();
	}

	if (outName.empty())
		throw Error("Empty name.");

	SkipWhitespace(cr);
}

void ApophysisGradient::SkipGradientContent( CharReader &cr )
{
	while (!QuerySymbol(cr, '}'))
	{
		// Skip single "name=value" pair.
		SkipIdentifier(cr);
		ExpectSymbol(cr, '=');
		SkipValue(cr);
	}

	SkipWhitespace(cr);
}

void ApophysisGradient::ParseGradientContent( CharReader &cr )
{
	uint lastIndex = 0;
	string s;
	while (!QuerySymbol(cr, '}'))
	{
		// Parse single "name=value" pair.

		ParseIdentifier(s, cr);
		if (s == "title" || s == "smooth")
		{
			ExpectSymbol(cr, '=');
			SkipValue(cr);
		}
		else
		{
			if (s != "index")
				throw Error("\"index\" expected.");

			ExpectSymbol(cr, '=');

			ParseIdentifier(s, cr);
			uint newIndex;
			MustStrToSth(&newIndex, s);
			if (newIndex < lastIndex || newIndex >= _countof(m_Colors))
				throw Error("Invalid index.");

			ExpectIdentifier(cr, "color");
			ExpectSymbol(cr, '=');

			ParseIdentifier(s, cr);
			COLOR newColor;
			MustStrToSth(&newColor.ARGB, s);
			std::swap(newColor.R, newColor.B);

			ColorToColorf(&m_Colors[newIndex], newColor);
			// Not neighbouding indices - linear interpolation between.
			if (newIndex > lastIndex+1)
			{
				float tStep = 1.f / (newIndex - lastIndex);
				float t = tStep;
				for (uint i = lastIndex+1; i < newIndex; i++, t += tStep)
					Lerp(&m_Colors[i], m_Colors[lastIndex], m_Colors[newIndex], t);
			}

			lastIndex = newIndex;
		}
	}

	for (uint i = 0; i < _countof(m_Colors); i++)
		m_Colors[i].A = 1.f;

	SkipWhitespace(cr);
}

void ApophysisGradient::GetColorAt( COLORF &out, float t )
{
	Interpolate(out, m_Colors, _countof(m_Colors), t);
}

void ApophysisGradient::SkipWhitespace( CharReader &cr )
{
	char ch;
	while (cr.PeekChar(&ch) && CharIsWhitespace(ch))
		cr.SkipChar();
}

void ApophysisGradient::ExpectSymbol( CharReader &cr, char ch )
{
	char readCh = cr.MustReadChar();
	if (readCh != ch)
		throw Error(Format("Expected symbol (#).") % ch);

	SkipWhitespace(cr);
}

void ApophysisGradient::ExpectIdentifier( CharReader &cr, const string &s )
{
	for (uint i = 0; i < s.length(); i++)
	{
		char readCh = cr.MustReadChar();
		if (readCh != s[i])
			throw Error(Format("Expected \"#\".") % s);
	}

	SkipWhitespace(cr);
}

bool ApophysisGradient::QuerySymbol( CharReader &cr, char ch )
{
	char readCh;
	if (!cr.PeekChar(&readCh))
		return false;
	return ch == readCh;
}

void ApophysisGradient::SkipIdentifier( CharReader &cr )
{
	bool atLeastOne = false;
	char ch;
	while (cr.PeekChar(&ch) && CharInIdentifier(ch))
	{
		cr.SkipChar();
		atLeastOne = true;
	}

	if (!atLeastOne)
		throw Error("Identifier expected.");

	SkipWhitespace(cr);
}

bool ApophysisGradient::CharInIdentifier( char ch )
{
	return CharIsAlphaNumeric(ch) || ch == '-' || ch == '.';
}

void ApophysisGradient::SkipValue( CharReader &cr )
{
	char ch;
	if (cr.PeekChar(&ch) && ch == '"')
		SkipString(cr);
	else
		SkipIdentifier(cr);
}

void ApophysisGradient::SkipString( CharReader &cr )
{
	ExpectSymbol(cr, '"');
	char ch;
	do {
		ch = cr.MustReadChar();
	}
	while (ch != '"');

	SkipWhitespace(cr);
}


GimpGradient::GimpGradient()
: m_BackgroundColor(COLORF_WHITE)
, m_ForegroundColor(COLORF_BLACK)
{

}

void GimpGradient::Load( const string &filePath )
{
	ERR_TRY;

	FileStream fs(filePath, FM_READ);
	CharReader cr(&fs);
	string s;
	
	// Header line - check
	cr.MustReadLine(&s);
	if (s != "GIMP Gradient")
		throw Error("Invalid file header.");

	// Name line - skip
	cr.MustReadLine(&s);
	
	// Number of segments
	cr.MustReadLine(&s);
	uint segCount;
	MustStrToSth(&segCount, s);

	// Segments
	m_Segments.resize(segCount);
	STRING_VECTOR sv;
	for (uint seg_i = 0; seg_i < segCount; seg_i++)
	{
		cr.MustReadLine(&s);
		Split(&sv, s, " ");
		if (sv.size() != 13 && sv.size() != 15)
			throw Error(Format("Error in segment #") % seg_i);
		GimpGradientSegment &seg = m_Segments[seg_i];
		
		MustStrToSth(&seg.left,   sv[0]);
		MustStrToSth(&seg.middle, sv[1]);
		MustStrToSth(&seg.right,  sv[2]);
	
		MustStrToSth(&seg.left_color.R, sv[3]);
		MustStrToSth(&seg.left_color.G, sv[4]);
		MustStrToSth(&seg.left_color.B, sv[5]);
		MustStrToSth(&seg.left_color.A, sv[6]);

		MustStrToSth(&seg.right_color.R, sv[7]);
		MustStrToSth(&seg.right_color.G, sv[8]);
		MustStrToSth(&seg.right_color.B, sv[9]);
		MustStrToSth(&seg.right_color.A, sv[10]);

		MustStrToSth((uint*)&seg.type,  sv[11]);
		MustStrToSth((uint*)&seg.color, sv[12]);

		if (sv.size() == 15)
		{
			MustStrToSth((uint*)&seg.left_color_type,  sv[13]);
			MustStrToSth((uint*)&seg.right_color_type, sv[14]);
		}
		else
			seg.left_color_type = seg.right_color_type = GIMP_GRADIENT_COLOR_FIXED;
	}

	ERR_CATCH("Cannot load GIMP gradient from \"" + filePath + "\"");
}

void GimpGradient::GetColorAt( COLORF &out, float pos )
{
	if (m_Segments.empty())
		out = m_BackgroundColor;
	else
	{
		pos = minmax(0.f, pos, 1.f);
		const GimpGradientSegment *seg = GetSegmentAtInterval(pos);
		float seg_len = seg->right - seg->left;
		float middle;
		if (seg_len < FLT_EPSILON)
		{
			middle = 0.5f;
			pos = 0.5f;
		}
		else
		{
			float seg_len_inv = 1.f / seg_len;
			middle = (seg->middle - seg->left) * seg_len_inv;
			pos = (pos - seg->left) * seg_len_inv;
		}

		float factor;
		switch (seg->type)
		{
		case GIMP_GRADIENT_SEGMENT_LINEAR:
			factor = CalcLinearFactor(middle, pos);
			break;
		case GIMP_GRADIENT_SEGMENT_CURVED:
			factor = CalcCurvedFactor(middle, pos);
			break;
		case GIMP_GRADIENT_SEGMENT_SINE:
			factor = CalcSineFactor(middle, pos);
			break;
		case GIMP_GRADIENT_SEGMENT_SPHERE_INCREASING:
			factor = CalcSphereIncreasingFactor(middle, pos);
			break;
		case GIMP_GRADIENT_SEGMENT_SPHERE_DECREASING:
			factor = CalcSphereDecreasingFactor(middle, pos);
			break;
		default:
			assert(0);
			factor = 0.5f;
		}

		COLORF left_color;
		switch (seg->left_color_type)
		{
		case GIMP_GRADIENT_COLOR_FIXED:
			left_color = seg->left_color;
			break;
		case GIMP_GRADIENT_COLOR_FOREGROUND:
		case GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT:
			left_color = m_ForegroundColor;
			if (seg->left_color_type == GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT)
				left_color.A = 0.f;
			break;
		case GIMP_GRADIENT_COLOR_BACKGROUND:
		case GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT:
			left_color = m_BackgroundColor;
			if (seg->left_color_type == GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT)
				left_color.A = 0.f;
			break;
		}

		COLORF right_color;
		switch (seg->right_color_type)
		{
		case GIMP_GRADIENT_COLOR_FIXED:
			right_color = seg->right_color;
			break;
		case GIMP_GRADIENT_COLOR_FOREGROUND:
		case GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT:
			right_color = m_ForegroundColor;
			if (seg->right_color_type == GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT)
				right_color.A = 0.f;
			break;
		case GIMP_GRADIENT_COLOR_BACKGROUND:
		case GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT:
			right_color = m_BackgroundColor;
			if (seg->right_color_type == GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT)
				right_color.A = 0.f;
			break;
		}

		if (seg->color == GIMP_GRADIENT_SEGMENT_RGB)
		{
			out.R = Lerp(left_color.R, right_color.R, factor);
			out.G = Lerp(left_color.G, right_color.G, factor);
			out.B = Lerp(left_color.B, right_color.B, factor);
		}
		else
		{
			GimpHSV left_hsv, right_hsv;
			GimpRgbToHsv(left_hsv, left_color);
			GimpRgbToHsv(right_hsv, right_color);

			left_hsv.s = Lerp(left_hsv.s, right_hsv.s, factor);
			left_hsv.v = Lerp(left_hsv.v, right_hsv.v, factor);

			switch (seg->color)
			{
			case GIMP_GRADIENT_SEGMENT_HSV_CCW:
				if (left_hsv.h < right_hsv.h)
					left_hsv.h += (right_hsv.h - left_hsv.h) * factor;
				else
				{
					left_hsv.h += (1.f - (left_hsv.h - right_hsv.h)) * factor;
					if (left_hsv.h > 1.f)
						left_hsv.h -= 1.f;
				}
				break;
			case GIMP_GRADIENT_SEGMENT_HSV_CW:
				if (right_hsv.h < left_hsv.h)
					left_hsv.h -= (left_hsv.h - right_hsv.h) * factor;
				else
				{
					left_hsv.h -= (1.f - (right_hsv.h - left_hsv.h)) * factor;
					if (left_hsv.h < 0.f)
						left_hsv.h += 1.f;
				}
				break;
			default:
				assert(0);
			}

			GimpHsvToRgb(out, left_hsv);
		}

		out.A = Lerp(left_color.A, right_color.A, factor);
	}
}

const GimpGradient::GimpGradientSegment * GimpGradient::GetSegmentAtInterval( float t ) const
{
	size_t index = std::distance(m_Segments.begin(), FirstNotLessIndex(m_Segments.begin(), m_Segments.end(), t, &GimpGradientSegment::KeyCmp));
	if (index == m_Segments.size())
		index--;
	return &m_Segments[index];
}

float GimpGradient::CalcLinearFactor( float middle, float pos )
{
	if (pos <= middle)
	{
		if (middle < FLT_EPSILON)
			return 0.0f;
		else
			return 0.5f * pos / middle;
	}
	else
	{
		pos -= middle;
		middle = 1.0f - middle;

		if (middle < FLT_EPSILON)
			return 1.0f;
		else
			return 0.5f + 0.5f * pos / middle;
	}
}

float GimpGradient::CalcCurvedFactor( float middle, float pos )
{
	if (middle < FLT_EPSILON)
		middle = FLT_EPSILON;
	return powf (pos, logf (0.5f) / logf (middle));
}

float GimpGradient::CalcSineFactor( float middle, float pos )
{
	pos = CalcLinearFactor (middle, pos);
	return (sinf ((-PI / 2.0f) + PI * pos) + 1.0f) / 2.0f;
}

float GimpGradient::CalcSphereIncreasingFactor( float middle, float pos )
{
	pos = CalcLinearFactor (middle, pos) - 1.0f;
	return sqrtf (1.0f - pos * pos);
}

float GimpGradient::CalcSphereDecreasingFactor( float middle, float pos )
{
	pos = CalcLinearFactor (middle, pos);

	/* Works for convex decreasing and concave increasing */
	return 1.0f - sqrtf(1.0f - pos * pos);
}

void GimpGradient::GimpRgbToHsv( GimpHSV &hsv, const COLORF &rgb )
{
	float max = max3(rgb.R, rgb.G, rgb.B);
	float min = min3(rgb.R, rgb.G, rgb.B);
	hsv.v = max;
	float delta = max - min;
	if (delta > 0.0001f)
	{
		hsv.s = delta / max;
		if (rgb.R == max)
		{
			hsv.h = (rgb.G - rgb.B) / delta;
			if (hsv.h < 0.0f)
				hsv.h += 6.0f;
		}
		else if (rgb.G == max)
			hsv.h = 2.0f + (rgb.B - rgb.R) / delta;
		else if (rgb.B == max)
			hsv.h = 4.0f + (rgb.R - rgb.G) / delta;
		hsv.h /= 6.0f;
	}
	else
		hsv.s = hsv.h = 0.0f;
	hsv.a = rgb.A;
}

void GimpGradient::GimpHsvToRgb( COLORF &rgb, const GimpHSV &hsv )
{
	if (hsv.s == 0.0f)
	{
		rgb.R = hsv.v;
		rgb.G = hsv.v;
		rgb.B = hsv.v;
	}
	else
	{
		float hue = hsv.h;
		if (hue == 1.0f)
			hue = 0.0f;
		hue *= 6.0f;
		int i = (int) hue;
		float f = hue - i;
		float w = hsv.v * (1.0f - hsv.s);
		float q = hsv.v * (1.0f - (hsv.s * f));
		float t = hsv.v * (1.0f - (hsv.s * (1.0f - f)));

		switch (i)
		{
		case 0:
			rgb.R = hsv.v;
			rgb.G = t;
			rgb.B = w;
			break;
		case 1:
			rgb.R = q;
			rgb.G = hsv.v;
			rgb.B = w;
			break;
		case 2:
			rgb.R = w;
			rgb.G = hsv.v;
			rgb.B = t;
			break;
		case 3:
			rgb.R = w;
			rgb.G = q;
			rgb.B = hsv.v;
			break;
		case 4:
			rgb.R = t;
			rgb.G = w;
			rgb.B = hsv.v;
			break;
		case 5:
			rgb.R = hsv.v;
			rgb.G = w;
			rgb.B = q;
			break;
		}
	}

	rgb.A = hsv.a;
}

} // namespace gradients
